// Miscellaneous Helper Functions used in the Standard Library

/* eslint-disable */

// Caching functions to speed up evaluation of slow redundant operations
let argCache = {};
let usedHashes = {};
let opNumber = 0;
let currentOp = '';
let currentLineNumber = 0;

/**
 * Hashes input arguments and checks the cache for that hash.
 * It returns a copy of the cached object if it exists, but will
 * call the `cacheMiss()` callback otherwise. The result will be
 * added to the cache if `GUIState["Cache?"]` is true.
 */
function CacheOp (args, cacheMiss) {
	// toReturn = cacheMiss();
	currentOp = args.callee.name;
	currentLineNumber = getCallingLocation()[0];
	postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber++, 'opType': args.callee.name } }); // Poor Man's Progress Indicator
	let toReturn = null;
	const curHash = ComputeHash(args);
	usedHashes[curHash] = curHash;
	const check = CheckCache(curHash);
	if (check && GUIState['Cache?']) {
		// console.log("HIT    "+ ComputeHash(args) +  ", " +ComputeHash(args, true));
		toReturn = new oc.TopoDS_Shape(check);
		toReturn.hash = check.hash;
	} else {
		// console.log("MISSED " + ComputeHash(args) + ", " + ComputeHash(args, true));
		toReturn = cacheMiss();
		toReturn.hash = curHash;
		if (GUIState['Cache?']) { AddToCache(curHash, toReturn); }
	}
	postMessage({ 'type': 'Progress', 'payload': { 'opNumber': opNumber, 'opType': null } }); // Poor Man's Progress Indicator
	return toReturn;
}

/** Returns the cached object if it exists, or null otherwise. */
function CheckCache (hash) { return argCache[hash] || null; }

/** Adds this `shape` to the cache, indexable by `hash`. */
function AddToCache (hash, shape) {
	const cacheShape = new oc.TopoDS_Shape(shape);
	cacheShape.hash = hash; // This is the cached version of the object
	argCache[hash] = cacheShape;
	return hash;
}

/** This function computes a 32-bit integer hash given a set of `arguments`.
 * If `raw` is true, the raw set of sanitized arguments will be returned instead. */
function ComputeHash (args, raw) {
	let argsString = JSON.stringify(args);
	argsString = argsString.replace(/("ptr":(-?[0-9]*?),)/g, '');
	argsString = argsString.replace(/("ptr":(-?[0-9]*))/g, '');
	if (argsString.includes('ptr')) { console.error('YOU DONE MESSED UP YOUR REGEX.'); }
	const hashString = args.callee.name + argsString;// + GUIState["MeshRes"];
	if (raw) { return hashString; }
	return stringToHash(hashString);
}

// Random Javascript Utilities

/** This function recursively traverses x and calls `callback()` on each subelement. */
function recursiveTraverse (x, callback) {
	if (Object.prototype.toString.call(x) === '[object Array]') {
		x.forEach(function (x1) {
			recursiveTraverse(x1, callback);
		});
	} else if ((typeof x === 'object') && (x !== null)) {
		if (x.HashCode) {
			callback(x);
		} else {
			for (const key in x) {
				if (x.hasOwnProperty(key)) {
					recursiveTraverse(x[key], callback);
				}
			}
		}
	} else {
		callback(x);
	}
}

/** This function returns a version of the `inputArray` without the `objectToRemove`. */
function Remove (inputArray, objectToRemove) {
	return inputArray.filter((el) => {
		return el.hash !== objectToRemove.hash ||
			el.ptr !== objectToRemove.ptr;
	});
}

/** This function returns true if item is indexable like an array. */
function isArrayLike (item) {
	return (
		Array.isArray(item) ||
		(!!item &&
			typeof item === 'object' &&
			item.hasOwnProperty('length') &&
			typeof item.length === 'number' &&
			item.length > 0 &&
			(item.length - 1) in item
		)
	);
}

/**  Mega Brittle Line Number Finding algorithm for Handle Backpropagation; only works in Chrome and FF.
 * Eventually this should be replaced with Microsoft's Typescript interpreter, but that's a big dependency...*/
function getCallingLocation () {
	const errorStack = (new Error()).stack;
	// console.log(errorStack);
	// console.log(navigator.userAgent);
	let lineAndColumn = [0, 0];

	let matchingString = ', <anonymous>:';
	if (navigator.userAgent.includes('Chrom')) {
		matchingString = ', <anonymous>:';
	} else if (navigator.userAgent.includes('Moz')) {
		matchingString = 'eval:';
	} else {
		lineAndColumn[0] = '-1';
		lineAndColumn[1] = '-1';
		return lineAndColumn;
	}

	errorStack.split('\n').forEach((line) => {
		if (line.includes(matchingString)) {
			lineAndColumn = line.split(matchingString)[1].split(':');
		}
	});
	lineAndColumn[0] = parseFloat(lineAndColumn[0]);
	lineAndColumn[1] = parseFloat(lineAndColumn[1]);

	return lineAndColumn;
}

/** This function converts either single dimensional
 * array or a gp_Pnt to a gp_Pnt.  Does not accept
 * `TopoDS_Vertex`'s yet! */
function convertToPnt (pnt) {
	let point = pnt; // Accept raw gp_Points if we got 'em
	if (point.length) {
		point = new oc.gp_Pnt(point[0], point[1], (point[2]) ? point[2] : 0);
	}
	return point;
}

/** This function converts a string to a 32bit integer. */
function stringToHash (string) {
	let hash = 0;
	if (string.length === 0) return hash;
	for (let i = 0; i < string.length; i++) {
		const char = string.charCodeAt(i);
		hash = ((hash << 5) - hash) + char;
		hash = hash & hash;
	}
	return hash;
}

function CantorPairing (x, y) {
	return ((x + y) * (x + y + 1)) / 2 + y;
}

export function potPack (boxes) {

	// calculate total box area and maximum box width
	var area = 0;
	var maxWidth = 0;

	for (var i$1 = 0, list = boxes; i$1 < list.length; i$1 += 1) {
		var box = list[i$1];

		area += box.w * box.h;
		maxWidth = Math.max(maxWidth, box.w);
	}

	// sort the boxes for insertion by height, descending
	boxes.sort(function (a, b) { return b.h - a.h; });

	// aim for a squarish resulting container,
	// slightly adjusted for sub-100% space utilization
	var startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth);

	// start with a single empty space, unbounded at the bottom
	var spaces = [{ x: 0, y: 0, w: startWidth, h: Infinity }];

	var width = 0;
	var height = 0;

	for (var i$2 = 0, list$1 = boxes; i$2 < list$1.length; i$2 += 1) {
		// look through spaces backwards so that we check smaller spaces first
		var box$1 = list$1[i$2];

		for (var i = spaces.length - 1; i >= 0; i--) {
			var space = spaces[i];

			// look for empty spaces that can accommodate the current box
			if (box$1.w > space.w || box$1.h > space.h) { continue; }

			// found the space; add the box to its top-left corner
			// |-------|-------|
			// |  box  |       |
			// |_______|       |
			// |         space |
			// |_______________|
			box$1.x = space.x;
			box$1.y = space.y;

			height = Math.max(height, box$1.y + box$1.h);
			width = Math.max(width, box$1.x + box$1.w);

			if (box$1.w === space.w && box$1.h === space.h) {
				// space matches the box exactly; remove it
				var last = spaces.pop();
				if (i < spaces.length) { spaces[i] = last; }

			} else if (box$1.h === space.h) {
				// space matches the box height; update it accordingly
				// |-------|---------------|
				// |  box  | updated space |
				// |_______|_______________|
				space.x += box$1.w;
				space.w -= box$1.w;

			} else if (box$1.w === space.w) {
				// space matches the box width; update it accordingly
				// |---------------|
				// |      box      |
				// |_______________|
				// | updated space |
				// |_______________|
				space.y += box$1.h;
				space.h -= box$1.h;

			} else {
				// otherwise the box splits the space into two spaces
				// |-------|-----------|
				// |  box  | new space |
				// |_______|___________|
				// | updated space     |
				// |___________________|
				spaces.push({
					x: space.x + box$1.w,
					y: space.y,
					w: space.w - box$1.w,
					h: box$1.h,
				});
				space.y += box$1.h;
				space.h -= box$1.h;
			}
			break;
		}
	}

	return {
		w: width, // container width
		h: height, // container height
		fill: (area / (width * height)) || 0, // space utilization
	};
}
